package edu.pitt.cs.cs2410pj2.datamodel;

import java.util.ArrayList;

import edu.pitt.cs.cs2410pj2.util.Parameters;

/**
 * A cache set contains several blocks
 * 
 * It would be best to design the blocks as a linkedlist, so that everytime we
 * just need to remove the bottom to do the LRU
 * 
 * Just for the simplicity of coding, we use an arraylist here
 * 
 * @author zhangfan
 * 
 */
public class L1CacheSet {
	int size = 1;
	int count = 0;
	boolean isFull = false;
	
	private int tileId;
	private int setIndex;
	
	
	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	public int getTileId() {
		return tileId;
	}

	public void setTileId(int tileId) {
		this.tileId = tileId;
	}

	public int getSetIndex() {
		return setIndex;
	}

	public void setSetIndex(int setIndex) {
		this.setIndex = setIndex;
	}

	public ArrayList<L1CacheEntry> getBlocks() {
		return blocks;
	}

	public void setBlocks(ArrayList<L1CacheEntry> blocks) {
		this.blocks = blocks;
	}

	public void setFull(boolean isFull) {
		this.isFull = isFull;
	}

	ArrayList<L1CacheEntry> blocks = new ArrayList<L1CacheEntry>();

	public L1CacheSet() {

	}

	public L1CacheSet(Parameters para) {
		size = (int) Math.pow(2, para.a1);
	}
	
	public L1CacheSet(Parameters para, int tileId, int setIndex) {
		size = (int) Math.pow(2, para.a1);
		this.tileId = tileId;
		this.setIndex = setIndex;
	}

	public boolean isFull() {
		return isFull;
	}

	// Looking for a block in the cacheset, returns null if not found
	public L1CacheEntry findBlock(int cacheTag) {
		for (int i = 0; i < count; i++) {
			if (blocks.get(i).getCacheTag() == cacheTag) {
				return blocks.get(i);
			}
		}
		return null;
	}

	// Update the time, supposed to be used together with findBlock
	public void updateCycle(int cacheTag, int timeCycle) {
		for (int i = 0; i < count; i++) {
			if (blocks.get(i).getCacheTag() == cacheTag) {
				blocks.get(i).setUpdateTime(timeCycle);
				return;
			}
		}
		return;
	}

	// adding a block, the cycle is the current time cycle
	public void addBlock(int cacheTag, int timeCycle,String data, String blockAddr) {
		L1CacheEntry block = new L1CacheEntry();
		block.setCacheTag(cacheTag);
		block.setUpdateTime(timeCycle);
		block.setData(data);
		block.setBlockAddr(blockAddr);
		blocks.add(block);
		count++;
		if (count == size)
			isFull = true;
	}

	// If implemented as a linkedlist, it should only take O(1) to do the
	// replace, but now it takes O(n)
	public L1CacheEntry replaceLRU(int cacheTag, int timeCycle, String data, String blockAddr) {
		int smallestCycle = blocks.get(0).getUpdateTime();
		int smallestEntryIndex = 0;
		for (int i = 0; i < size; i++) {
			if(blocks.get(i).getState()==State.INVALID) {
				smallestEntryIndex = i;
				break;
			}
			if (blocks.get(i).getUpdateTime() < smallestCycle) {
				smallestEntryIndex = i;
				smallestCycle = blocks.get(i).getUpdateTime();
			}
		}

		if (blocks.get(smallestEntryIndex).getState() == State.MODIFIED) {
			L1CacheEntry replaced = new L1CacheEntry();
			replaced.setCacheTag(blocks.get(smallestEntryIndex).getCacheTag());
			replaced.setState(blocks.get(smallestEntryIndex).getState());
			replaced.setData(blocks.get(smallestEntryIndex).getData());
			replaced.setBlockAddr(blocks.get(smallestEntryIndex).getBlockAddr());
			
			
			blocks.remove(smallestEntryIndex);
			count--;
			addBlock(cacheTag, timeCycle,data,blockAddr);
			return replaced;
		} else {
			// No need to do the copy, since this one can be directly replaced
			blocks.remove(smallestEntryIndex);
			count--;
			addBlock(cacheTag, timeCycle,data,blockAddr);
			return null;
		}
	}
	
	public String toString() {
		String msg = "";
		for(int i = 0;i<count;i++) {
			msg += blocks.get(i).toString()+"\n";
		}
		return msg;
	}

}
